package lumis.report.hourlypagecacheclear;

import lumis.portal.PortalException;
import lumis.portal.manager.ManagerFactory;
import lumis.report.IJRDataSourceProvider;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TemporalType;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRMapCollectionDataSource;

/**
 * Data source provider used for the hourly page cache cleaning report.
 * 
 * @version $Revision: 13092 $ $Date: 2011-05-28 18:19:06 -0300 (Sat, 28 May 2011) $
 * @since 6.0.0
 */
public class HourlyPageCacheClearJRDataSourceProvider implements IJRDataSourceProvider
{
	private static final int FILTER_TYPE_CONTAINS = 0;
	private static final int FILTER_TYPE_EQUALS = 1;
	private static final int FILTER_TYPE_STARTS_WITH = 2;
	private static final int FILTER_TYPE_ENDS_WITH = 3;
	private static final long HOUR = 1000L * 60 * 60;

	/**
	 * Returns the filter string for the given parameter based on filter type
	 * and if this is for groups or not.
	 * 
	 * @param parameterName
	 *            the desired parameter name.
	 * @param filterType
	 *            the filter type.
	 * @param isForGroups
	 *            indicates if this is for user groups or not.
	 * @return the filter string.
	 * @since 6.0.0
	 */
	private static final String getFilterString(final String parameterName, final Integer filterType, boolean isForGroups)
	{
		String ret = "";
		if (isForGroups)
		{
			ret = " like :" + parameterName + " escape '$'";
		}
		else
		{
			switch (filterType.intValue())
			{
				case FILTER_TYPE_CONTAINS:
				case FILTER_TYPE_STARTS_WITH:
				case FILTER_TYPE_ENDS_WITH:
					ret = " like :" + parameterName;
					break;
				case FILTER_TYPE_EQUALS:
					ret = "=:" + parameterName;
					break;
			}
		}
		return ret + " ";
	}

	/**
	 * Returns the filter value based on the filter type.
	 * 
	 * @param parameterValue
	 *            the parameter value.
	 * @param filterType
	 *            the tilter type.
	 * @param isForGroups
	 *            indicates if this is for user groups or not.
	 * @return the filter value.
	 * @since 6.0.0
	 */
	private static final String getFilterValueString(final String parameterValue, final Integer filterType, boolean isForGroups)
	{
		String ret = "";
		if (isForGroups)
		{
			switch (filterType.intValue())
			{
				case FILTER_TYPE_CONTAINS:
					ret = "%" + parameterValue + "%";
					break;
				case FILTER_TYPE_EQUALS:
					ret = "%$[" + parameterValue + "]%";
					break;
				case FILTER_TYPE_STARTS_WITH:
					ret = "%$[" + parameterValue + "%";
					break;
				case FILTER_TYPE_ENDS_WITH:
					ret = "%" + parameterValue + "$]%";
					break;
			}
		}
		else
		{
			switch (filterType.intValue())
			{
				case FILTER_TYPE_CONTAINS:
					ret = "%" + parameterValue + "%";
					break;
				case FILTER_TYPE_EQUALS:
					ret = parameterValue;
					break;
				case FILTER_TYPE_STARTS_WITH:
					ret = parameterValue + "%";
					break;
				case FILTER_TYPE_ENDS_WITH:
					ret = "%" + parameterValue;
					break;
			}
		}
		return ret;
	}

	/**
	 * Returns a {@link List list} of {@link Map maps} containing the data for
	 * the hourly page cache cleaning report.
	 * 
	 * @param startDate
	 *            the start date.
	 * @param endDate
	 *            the end date.
	 * @param rootChannelPath
	 *            the root channel path.
	 * @param channelPathFilterType
	 *            the channel path filter type.
	 * @param channelPath
	 *            the channel path filter
	 * @param userLoginFilterType
	 *            the user login filter type.
	 * @param userLogin
	 *            the user login filter.
	 * @param serverIdFilterType
	 *            the server identifier filter type.
	 * @param serverId
	 *            the service identifier filter.
	 * @param serviceIdFilterType
	 *            the service identifier filter type;
	 * @param serviceId
	 *            the service identifier filter.
	 * @param userGroupFilterType
	 *            the user groups filter type.
	 * @param userGroup
	 *            the user groups filter.
	 * @param includeEmptyRows
	 *            indicates if empty rows should be included.
	 * @return the data for report.
	 * @throws JRException
	 * @since 6.0.0
	 */
	@SuppressWarnings("unchecked")
	private static List<Map<String, Object>> readData(Date startDate, Date endDate, final String rootChannelPath,
			final Integer channelPathFilterType, final String channelPath, final Integer userLoginFilterType, final String userLogin,
			final Integer serverIdFilterType, final String serverId, final Integer serviceIdFilterType, final String serviceId,
			final Integer userGroupFilterType, final String userGroup, final boolean includeEmptyRows)
			throws JRException
	{
		if (startDate == null)
		{
			Calendar now = Calendar.getInstance();
			now.set(Calendar.HOUR_OF_DAY, 0);
			now.set(Calendar.MINUTE, 0);
			now.set(Calendar.SECOND, 0);
			now.set(Calendar.MILLISECOND, 0);
			startDate = new Date(now.getTimeInMillis());
		}
		if (endDate == null)
		{
			Calendar now = Calendar.getInstance();
			now.set(Calendar.HOUR_OF_DAY, 23);
			now.set(Calendar.MINUTE, 59);
			now.set(Calendar.SECOND, 59);
			endDate = new Date(now.getTimeInMillis());
		}
		else
		{
			Calendar end = Calendar.getInstance();
			end.setTimeInMillis(endDate.getTime());
			end.set(Calendar.HOUR_OF_DAY, 23);
			end.set(Calendar.MINUTE, 59);
			end.set(Calendar.SECOND, 59);
			endDate = new Date(end.getTimeInMillis());
		}
		
		endDate = new Date(endDate.getTime() + HOUR);

		// read data from database
		EntityManager entityManager = ManagerFactory.getEntityManager();
		StringBuilder sb = new StringBuilder(1000);
		sb.append("select ");
		sb.append("		min(ed.startOfPeriod) as eventDate, ");
		sb.append("		sum(edm.hits) as hits ");
		sb.append("from ");
		sb.append("		Event e ");
		sb.append("		inner join e.eventDatas ed ");
		sb.append("		inner join ed.measures edm ");

		sb.append("	inner join ed.aggregations channelPathDA ");

		boolean hasUserLogin = userLogin != null && !("".equals(userLogin));
		if (hasUserLogin)
		{
			sb.append("	inner join ed.aggregations userLoginDA ");
		}

		boolean hasServerId = serverId != null && !("".equals(serverId));
		if (hasServerId)
		{
			sb.append("	inner join ed.aggregations serverIdDA ");
		}

		boolean hasServiceId = serviceId != null && !("".equals(serviceId));
		if (hasServiceId)
		{
			sb.append("	inner join ed.aggregations serviceIdDA ");
		}

		boolean hasUserGroup = userGroup != null && !("".equals(userGroup));
		if (hasUserGroup)
		{
			sb.append("	inner join ed.aggregations userGroupDA ");
		}

		sb.append("where ");
		sb.append("		e.eventKey='lumis.portal.page.cache.ev.clear' ");
		sb.append("		and edm.measureType.measureTypeKey='lumis.portal.page.cache.mt.clearcount' ");
		sb.append("		and ed.startOfPeriod >= :startDate and ed.startOfPeriod < :endDate ");

		sb.append("	and channelPathDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.channel.path' ");

		sb.append("	and (");
		boolean hasChannelPath = channelPath != null && !"".equals(channelPath);
		if (hasChannelPath)
		{
			sb.append("channelPathDA.value ");
			switch (channelPathFilterType.intValue())
			{
				case FILTER_TYPE_EQUALS:
					sb.append("=");
					break;
				default:
					sb.append("like");
					break;
			}
			sb.append(" :channelPath and ");
		}
		sb.append(" channelPathDA.value like :insideRootChannelPath) ");

		if (hasUserLogin)
		{
			sb.append("	and userLoginDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.user.login' ");
			sb.append("	and userLoginDA.value ");
			sb.append(getFilterString("userLogin", userLoginFilterType, false));
		}

		if (hasServerId)
		{
			sb.append("	and serverIdDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.server.id' ");
			sb.append("	and serverIdDA.value ");
			sb.append(getFilterString("serverId", userLoginFilterType, false));
		}

		if (hasServiceId)
		{
			sb.append("	and serviceIdDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.service.id' ");
			sb.append("	and serviceIdDA.value ");
			sb.append(getFilterString("serviceId", userLoginFilterType, false));
		}

		if (hasUserGroup)
		{
			sb.append("	and userGroupDA.aggregationType.aggregationTypeKey='lumis.portal.monitor.at.user.groups' ");
			sb.append("	and userGroupDA.value ");
			sb.append(getFilterString("userGroup", userLoginFilterType, true));
		}

		sb.append("group by year(ed.startOfPeriod), month(ed.startOfPeriod), day(ed.startOfPeriod), hour(ed.startOfPeriod) order by 1");

		Query query = entityManager.createQuery(sb.toString());
		query = query.setParameter("startDate", startDate, TemporalType.TIMESTAMP);
		query = query.setParameter("endDate", endDate, TemporalType.TIMESTAMP);
		query = query.setParameter("insideRootChannelPath", rootChannelPath + "/%");

		if (hasChannelPath)
		{
			String channelPathParam = null;
			switch (channelPathFilterType.intValue())
			{
				case FILTER_TYPE_CONTAINS:
					channelPathParam = "%" + channelPath + "%";
					break;
				case FILTER_TYPE_EQUALS:
					channelPathParam = rootChannelPath + "/" + channelPath;
					break;
				case FILTER_TYPE_STARTS_WITH:
					channelPathParam = "%/" + channelPath + "%";
					break;
				case FILTER_TYPE_ENDS_WITH:
					channelPathParam = "%" + channelPath;
					break;
			}
			query = query.setParameter("channelPath", channelPathParam);
		}

		if (hasUserLogin)
		{
			query = query.setParameter("userLogin", getFilterValueString(userLogin, userLoginFilterType, false));
		}

		if (hasServerId)
		{
			query = query.setParameter("serverId", getFilterValueString(serverId, serverIdFilterType, false));
		}

		if (hasServiceId)
		{
			query = query.setParameter("serviceId", getFilterValueString(serviceId, serviceIdFilterType, false));
		}

		if (hasUserGroup)
		{
			query = query.setParameter("userGroup", getFilterValueString(userGroup, userGroupFilterType, true));
		}

		List<Object[]> clearCacheData = query.getResultList();
		
		List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(100);
		for (long date = startDate.getTime(); date < endDate.getTime(); date += HOUR)
		{
			Object[] clearCacheRow = getRowDataByHour(clearCacheData, date);

			if (!includeEmptyRows && clearCacheRow == null)
				continue;

			long numClearCache = 0;
			if (clearCacheRow != null)
				numClearCache = ((Number) clearCacheRow[1]).longValue();

			Map<String, Object> row = new HashMap<String, Object>();
			row.put("eventDate", new Timestamp(date));
			row.put("eventDateEnd", new Timestamp(date + HOUR));
			row.put("numClearCache", numClearCache);
			result.add(row);
		}

		return result;
	}

	/**
	 * Returns the row in the given list that is for the specified date.
	 * 
	 * @param rows
	 *            the rows. The first column is expected to be the startOfPeriod
	 *            value.
	 * @param date
	 *            the date desired.
	 * @return the row of the specified date.
	 * @since 6.0.0
	 */
	private static final Object[] getRowDataByHour(final List<Object[]> rows, final long date)
	{
		for (Object[] row : rows)
		{
			Date startOfPeriod = (Date) row[0];
			long timeDiff = startOfPeriod.getTime() - date;
			if (timeDiff >= 0 && timeDiff < HOUR)
				return row;
		}
		return null;
	}

	/**
	 * Returns a {@link JRDataSource data source} for the hourly page cache
	 * cleaning report. Used in the table.
	 * 
	 * @param startDate
	 *            the start date.
	 * @param endDate
	 *            the end date.
	 * @param rootChannelPath
	 *            the root channel path.
	 * @param channelPathFilterType
	 *            the channel path filter type.
	 * @param channelPath
	 *            the channel path filter
	 * @param userLoginFilterType
	 *            the user login filter type.
	 * @param userLogin
	 *            the user login filter.
	 * @param serverIdFilterType
	 *            the server identifier filter type.
	 * @param serverId
	 *            the service identifier filter.
	 * @param serviceIdFilterType
	 *            the service identifier filter type;
	 * @param serviceId
	 *            the service identifier filter.
	 * @param userGroupFilterType
	 *            the user groups filter type.
	 * @param userGroup
	 *            the user groups filter.
	 * @return the data for report.
	 * @throws JRException
	 * @since 6.0.0
	 */
	public static JRDataSource getDataForTable(final Date startDate, final Date endDate, final String rootChannelPath,
			final Integer channelPathFilterType, final String channelPath, final Integer userLoginFilterType, final String userLogin,
			final Integer serverIdFilterType, final String serverId, final Integer serviceIdFilterType, final String serviceId,
			final Integer userGroupFilterType, String userGroup) throws JRException
	{
		return new JRMapCollectionDataSource(readData(startDate, endDate, rootChannelPath, channelPathFilterType, channelPath,
				userLoginFilterType, userLogin, serverIdFilterType, serverId, serviceIdFilterType, serviceId, userGroupFilterType,
				userGroup, false));
	}

	/**
	 * Returns a {@link JRDataSource data source} for the hourly page cache
	 * cleaning report. Used in the chart.
	 * 
	 * @param startDate
	 *            the start date.
	 * @param endDate
	 *            the end date.
	 * @param rootChannelPath
	 *            the root channel path.
	 * @param channelPathFilterType
	 *            the channel path filter type.
	 * @param channelPath
	 *            the channel path filter
	 * @param userLoginFilterType
	 *            the user login filter type.
	 * @param userLogin
	 *            the user login filter.
	 * @param serverIdFilterType
	 *            the server identifier filter type.
	 * @param serverId
	 *            the service identifier filter.
	 * @param serviceIdFilterType
	 *            the service identifier filter type;
	 * @param serviceId
	 *            the service identifier filter.
	 * @param userGroupFilterType
	 *            the user groups filter type.
	 * @param userGroup
	 *            the user groups filter.
	 * @return the data for report.
	 * @throws JRException
	 * @since 6.0.0
	 */
	public static JRDataSource getDataForChart(final Date startDate, final Date endDate, final String rootChannelPath,
			final Integer channelPathFilterType, final String channelPath, final Integer userLoginFilterType, final String userLogin,
			final Integer serverIdFilterType, final String serverId, final Integer serviceIdFilterType, final String serviceId,
			final Integer userGroupFilterType, final String userGroup) throws JRException
	{
		return new JRMapCollectionDataSource(readData(startDate, endDate, rootChannelPath, channelPathFilterType, channelPath,
				userLoginFilterType, userLogin, serverIdFilterType, serverId, serviceIdFilterType, serviceId, userGroupFilterType,
				userGroup, true));
	}

	public JRDataSource create(final JasperReport jasperreport, final Map<String, Object> parameters) throws JRException, PortalException
	{
		final Date startDate = (Date) parameters.get("startDate");
		final Date endDate = (Date) parameters.get("endDate");
		final Integer userLoginFilterType = (Integer) parameters.get("userLoginFilterType");
		final String userLogin = (String) parameters.get("userLogin");
		final String rootChannelPath = (String) parameters.get("rootChannelPath");
		final Integer channelPathFilterType = (Integer) parameters.get("channelPathFilterType");
		final String channelPath = (String) parameters.get("channelPath");
		final Integer serverIdFilterType = (Integer) parameters.get("serverIdFilterType");
		final String serverId = (String) parameters.get("serverId");
		final Integer serviceIdFilterType = (Integer) parameters.get("serviceIdFilterType");
		final String serviceId = (String) parameters.get("serviceId");
		final Integer userGroupFilterType = (Integer) parameters.get("userGroupFilterType");
		final String userGroup = (String) parameters.get("userGroup");

		return getDataForTable(startDate, endDate, rootChannelPath, channelPathFilterType, channelPath, userLoginFilterType, userLogin,
				serverIdFilterType, serverId, serviceIdFilterType, serviceId, userGroupFilterType, userGroup);
	}

	public void dispose(JRDataSource jrDataSource) throws JRException, PortalException
	{
	}
}
